package com.ibm.cps.model;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.log4j.Logger;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Throwables;
import com.ibm.cps.message.MessageFields;
import com.ibm.cps.newmessage.AbstractDataSourceMetadata;
import com.ibm.cps.newmessage.DataSourceMetadataFactory;
import com.ibm.factories.DataSourceFactory;
import com.ibm.interfaces.IPersistency;
import com.ibm.mongo.MongoPersistency;
import com.ibm.util.ErrorCode;
import com.ibm.util.JSONUtility;
import com.ibm.util.exception.CPSException;

public class DataSourceMonitor {

	// private static class DataSourceMonitorInstance{
	// private static final DataSourceMonitor instance = new
	// DataSourceMonitor();
	// }
	//
	// public static final DataSourceMonitor getInstance(){
	// return DataSourceMonitorInstance.instance;
	// }
	private static DataSourceMonitor monitor;

	// private Map<String, AbstractDataSourceMetadata> name2DataSource;
	private Map<String, DataSourceFactory> tenantid2DSFactory;
	private boolean isFirst = true;
	private Logger logger = Logger.getLogger(DataSourceMonitor.class);
	private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

	public static DataSourceMonitor getInstance() {
		if (monitor == null) {
			monitor = new DataSourceMonitor();
		}
		return monitor;
	}

	private DataSourceMonitor() {
		tenantid2DSFactory = new HashMap<String, DataSourceFactory>();
	}

	public DataSourceFactory getFactory(String tenantid) throws CPSException {
		cacheDatasources();
		if (tenantid2DSFactory.get(tenantid) == null) {
			tenantid2DSFactory.put(tenantid,new DataSourceFactory());
			//	throw new CPSException(ErrorCode.NULL_FIELD_ERROR, "null factory");
		}
		return tenantid2DSFactory.get(tenantid);
	}

	// append tenantid,schemaid in the objectNode
	private static void appendFields(ObjectNode objectNode, String tenantid)
			throws CPSException {
		// checkValidateId(objectNode, MessageFields.NAME);
		checkValidateId(objectNode, MessageFields.SCHEMAID);
		checkValidateId(objectNode, MessageFields.TENANTID);
		// JSONUtility.assignRandomValue(objectNode, MessageFields.NAME);
		JSONUtility.assignRandomValue(objectNode, MessageFields.SCHEMAID);
		objectNode.put(MessageFields.TENANTID, tenantid);
	}

	private static void checkValidateId(ObjectNode node, String fieldName)
			throws CPSException {
		String id = null;
		try {
			if (node.has(fieldName)) {
				id = node.get(fieldName).asText();
				Hex.decodeHex(id.toCharArray());
			}
		} catch (Exception e) {
			throw new CPSException(ErrorCode.PARSE_ERROR_CODE, "Invalid "
					+ fieldName + " " + id + ", it should be hexadecimal.");
		}
	}

	public AbstractDataSourceMetadata validateDataSource(String tenantid,
			ObjectNode objectNode) throws CPSException {
		cacheDatasources();
		appendFields(objectNode, tenantid);
		AbstractDataSourceMetadata metadata = DataSourceMetadataFactory
				.parseDataSourceMetadata(objectNode.toString());
		
		DataSourceFactory factory = tenantid2DSFactory.get(tenantid);
		if(factory == null || !factory.containsDataSource(metadata.getName())){
			addDataSource(tenantid, metadata);
		} else {
			throw new CPSException(ErrorCode.ALREADY_EXIST_CODE, "Data source "
					+ metadata.getName() + " already existing.");
		}
		return metadata;
	}

	private void cacheDatasources() {
		try {
			lock.readLock().lock();
			try {
				if (isFirst) {
					lock.readLock().unlock();
					lock.writeLock().lock();
					try {
						IPersistency persistency = MongoPersistency
								.getInstace();
						Collection<String> dataSources = persistency
								.getDataSource(null, null);
						if (dataSources == null) {
							return;
						}
						for (String source : dataSources) {
							try {
								AbstractDataSourceMetadata metadata = DataSourceMetadataFactory
										.parseDataSourceMetadata(source);
								String tenantid = metadata.getTenantid();
								addDataSource(tenantid, metadata);
							} catch (CPSException e) {
								logger.error("Error datasource metadata stored in mongo:"
										+ source);
								lock.readLock().lock();
								throw new CPSException(
										ErrorCode.INVALID_INPUT_ERROR,
										"Error datasource metadata stored in mongo"
												+ source);
							}
						}
						isFirst = false;
						lock.readLock().lock();
					} finally {
						lock.writeLock().unlock();
					}
				}
			} finally {
				lock.readLock().unlock();
			}
		} catch (CPSException e) {
			logger.error("Failed to get datasource metadata stored in mongo.");
			logger.error(Throwables.getStackTraceAsString(e));
		}
	}

	private void addDataSource(String tenantid,
			AbstractDataSourceMetadata metadata) {
		if (tenantid2DSFactory.get(tenantid) == null) {
			tenantid2DSFactory.put(tenantid, new DataSourceFactory());
		}
		tenantid2DSFactory.get(tenantid).addDataSource(metadata);
	}

	public void deleteDataSource(String tenantid,
			AbstractDataSourceMetadata dataSource) throws CPSException {
		cacheDatasources();
		if (tenantid2DSFactory.get(tenantid) == null
				|| !tenantid2DSFactory.get(tenantid).containsDataSource(
						dataSource.getName())) {
			throw new CPSException(ErrorCode.INVALID_REQUEST_CODE,
					"Data source " + dataSource.getName()
							+ " was not existing.");
		}

		tenantid2DSFactory.get(tenantid).deleteDataSource(dataSource);
		logger.info("Successfully deleted data source " + dataSource.getInput());
	}

	public static void main(String[] args) throws DecoderException {
		Hex.decodeHex("1234567890123456789012".toCharArray());
	}
}
